Repository navigation
feat(server): improve configuration file handling - #423
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughNest workspace config under Changes
Sequence DiagramsequenceDiagram
participant Client
participant MasterServer
participant Config as ConfigLoader
participant Workspace
participant Compiler
Client->>MasterServer: initialize(with initializationOptions JSON)
MasterServer->>MasterServer: store init_options_json
Client->>MasterServer: Initialized
MasterServer->>Config: load_from_json(init_options_json)
alt parsed OK
Config-->>MasterServer: CliceConfig (project + compiled_rules)
else fallback
MasterServer->>Config: load_from_workspace(workspace_root)
Config-->>MasterServer: CliceConfig (from TOML/defaults)
end
MasterServer->>Workspace: initialize workspace with config (cache/index paths)
MasterServer->>MasterServer: clear init_options_json
Client->>MasterServer: request compile args (file)
MasterServer->>Compiler: fill_compile_args(path)
Compiler->>Config: match_rules(path)
Config-->>Compiler: append/remove flags
Compiler->>Workspace: cdb.lookup(path, CommandOptions(with rules))
Workspace-->>Compiler: compile args
Compiler-->>MasterServer: resolved compile args
MasterServer-->>Client: respond with compile args
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/server/compiler.cpp (1)
48-53:⚠️ Potential issue | 🟠 MajorApply
match_rules()in the lazy module dependency resolver too.
fill_compile_argsnow applies rule-based flags, but at Line 49-53 the compile-graph resolver still doesworkspace.cdb.lookupwithoutappend/remove. That can produce inconsistent module dependency scanning vs actual compilation.Suggested fix
auto resolve = [this](std::uint32_t path_id) -> llvm::SmallVector<std::uint32_t> { auto file_path = workspace.path_pool.resolve(path_id); - auto results = - workspace.cdb.lookup(file_path, {.query_toolchain = true, .suppress_logging = true}); + std::vector<std::string> rule_append, rule_remove; + workspace.config.match_rules(file_path, rule_append, rule_remove); + auto results = workspace.cdb.lookup(file_path, + {.query_toolchain = true, + .suppress_logging = true, + .remove = rule_remove, + .append = rule_append}); if(results.empty()) return {};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/compiler.cpp` around lines 48 - 53, The lazy module dependency resolver (the resolve lambda) calls workspace.cdb.lookup without applying rule-based flag adjustments, causing inconsistent scanning versus fill_compile_args; modify resolve to call match_rules() (or otherwise apply append/remove from the rule matcher) to the compile args returned by workspace.cdb.lookup so the same rule-driven flags are appended/removed as in fill_compile_args; locate the resolve lambda and ensure it uses the same match_rules()/append/remove logic (or a helper used by fill_compile_args) before returning the resolved compile args/results.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/server/config.cpp`:
- Around line 77-82: apply_defaults currently only performs variable
substitution via substitute_workspace for entries in p.compile_commands_paths
but doesn't anchor relative paths to the workspace root, so relative entries
like "build" resolve against the process CWD; after calling substitute_workspace
for each path in p.compile_commands_paths, check if the path is not absolute
(use std::filesystem::path::is_absolute) and if so replace it with
workspace_root / path (and normalize, e.g., lexically_normal) so
compile_commands_paths entries are anchored to workspace_root; update the loop
in apply_defaults that iterates p.compile_commands_paths to perform this
relative->absolute anchoring after substitution.
In `@src/server/config.h`:
- Around line 27-75: The new CliceConfig nests project settings but
deserialization still expects old flat keys, so add a migration shim in the
loading paths: update CliceConfig::load and CliceConfig::load_from_json to
detect legacy top-level keys (e.g., cache_dir, index_dir, logging_dir,
enable_indexing, idle_timeout_ms, clang_tidy, max_active_file,
stateful_worker_count, stateless_worker_count, worker_memory_limit,
compile_commands_paths) and move them into a ProjectConfig instance (or
translate them into a temporary JSON/TOML map) before populating
CliceConfig::project; ensure apply_defaults and match_rules continue to operate
on the migrated project and preserve backwards compatibility for
initializationOptions and clice.toml users.
In `@src/server/master_server.cpp`:
- Around line 101-121: The current auto-scan uses directory_iterator to pick the
first compile_commands.json and is non-deterministic; modify the scan inside the
directory iteration (where try_candidate, cdb_path, and workspace_root are used)
to collect all matching candidate paths into a vector, sort that vector (e.g.,
lexicographically), then choose a stable winner (e.g., the first element) or
log/warn if multiple matches exist before assigning cdb_path; ensure you still
short-circuit if a match was already found by the initial
try_candidate(workspace_root) check.
---
Outside diff comments:
In `@src/server/compiler.cpp`:
- Around line 48-53: The lazy module dependency resolver (the resolve lambda)
calls workspace.cdb.lookup without applying rule-based flag adjustments, causing
inconsistent scanning versus fill_compile_args; modify resolve to call
match_rules() (or otherwise apply append/remove from the rule matcher) to the
compile args returned by workspace.cdb.lookup so the same rule-driven flags are
appended/removed as in fill_compile_args; locate the resolve lambda and ensure
it uses the same match_rules()/append/remove logic (or a helper used by
fill_compile_args) before returning the resolved compile args/results.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d276d414-56a8-44bc-814e-826a6b37ab73
📒 Files selected for processing (11)
src/server/compiler.cppsrc/server/config.cppsrc/server/config.hsrc/server/indexer.cppsrc/server/master_server.cppsrc/server/master_server.hsrc/server/workspace.cppsrc/support/glob_pattern.cppsrc/support/glob_pattern.htests/integration/compilation/test_persistent_cache.pytests/unit/server/config_tests.cpp
5f8b358 to
5891b35
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
src/server/config.h (1)
52-59:⚠️ Potential issue | 🟠 MajorPreserve legacy flat config keys or migrate them explicitly.
Line 55 and Line 57 make
project/rulesthe only deserialized config surface, whilesrc/server/config.cppstill parses TOML/JSON directly intoCliceConfig. Existing configs using top-levelcache_dir,enable_indexing, worker counts, orcompile_commands_pathscan be silently ignored after upgrade unless a migration/alias layer is added.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/config.h` around lines 52 - 59, CliceConfig's deserializer currently only exposes the nested fields project and rules which will drop legacy top-level keys (e.g., cache_dir, enable_indexing, worker counts, compile_commands_paths) parsed in src/server/config.cpp; update the parsing/deserialization so it accepts legacy flat keys and maps them into the new nested structure (or perform an explicit migration/aliasing pass) before populating CliceConfig.project and CliceConfig.rules, leaving annotation<std::vector<CompiledRule>, serde_schema::skip> compiled_rules behavior unchanged; specifically modify the code that constructs CliceConfig in src/server/config.cpp to detect legacy keys and copy them into the corresponding ProjectConfig fields (and translate any worker/compile_commands settings to their new locations) so existing configs are preserved.src/server/master_server.cpp (1)
103-123:⚠️ Potential issue | 🟠 MajorMake CDB auto-scan deterministic when multiple subdirectories match.
Lines 116-120 still pick the first
compile_commands.jsonreturned bydirectory_iterator, whose order is filesystem-dependent. Workspaces withbuild-debug/andbuild-release/can load different CDBs across machines or runs; collect matches, sort them, and warn when more than one candidate exists.Proposed fix
- if(!try_candidate(workspace_root)) { + if(!try_candidate(workspace_root)) { + std::vector<std::string> candidates; std::error_code ec; for(llvm::sys::fs::directory_iterator it(workspace_root, ec), end; it != end && !ec; it.increment(ec)) { if(it->type() == llvm::sys::fs::file_type::directory_file) { - if(try_candidate(it->path())) - break; + auto candidate = path::join(it->path(), "compile_commands.json"); + if(llvm::sys::fs::exists(candidate)) + candidates.push_back(std::move(candidate)); } } + std::ranges::sort(candidates); + if(candidates.size() > 1) + LOG_WARN("Multiple compile_commands.json files found; using {}", candidates.front()); + if(!candidates.empty()) + cdb_path = std::move(candidates.front()); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/master_server.cpp` around lines 103 - 123, The current auto-scan uses directory_iterator order to set cdb_path via the try_candidate lambda, which is non-deterministic; change the logic to collect all matching compile_commands.json paths (use try_candidate or inline check) into a vector<string> (e.g., candidates), sort the vector lexicographically, then if candidates is non-empty set cdb_path = candidates.front(); if candidates.size() > 1 emit a warning listing the extra candidates (so users know which CDB was chosen). Update the block that currently iterates workspace_root and uses try_candidate to instead populate, sort, choose, and warn (referencing cdb_path, try_candidate, workspace_root, and the directory_iterator loop to locate where to change).src/server/config.cpp (1)
80-85:⚠️ Potential issue | 🟠 MajorAnchor relative project paths to
workspace_rootafter substitution.Line 84 still leaves configured
compile_commands_pathsrelative to the server process CWD; the same applies to user-provided cache/index/logging paths on Lines 81-83. A config value likecompile_commands_paths = ["build"]orcache_dir = ".clice"should resolve under the workspace, not wherever the server was launched.Proposed fix
- // Variable substitution on string fields. - substitute_workspace(p.cache_dir, workspace_root); - substitute_workspace(p.index_dir, workspace_root); - substitute_workspace(p.logging_dir, workspace_root); - for(auto& path: p.compile_commands_paths) - substitute_workspace(path, workspace_root); + // Variable substitution on string fields. + auto normalize_project_path = [&](std::string& value) { + substitute_workspace(value, workspace_root); + if(!value.empty() && !workspace_root.empty() && !llvm::sys::path::is_absolute(value)) + value = path::join(workspace_root, value); + }; + + normalize_project_path(p.cache_dir); + normalize_project_path(p.index_dir); + normalize_project_path(p.logging_dir); + for(auto& path: p.compile_commands_paths) + normalize_project_path(path);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/config.cpp` around lines 80 - 85, The substituted paths (p.cache_dir, p.index_dir, p.logging_dir and each entry in p.compile_commands_paths) must be anchored to workspace_root when they are relative; after calling substitute_workspace(...) detect if the resulting string is a relative path and, if so, prepend/resolve it against workspace_root (e.g. using std::filesystem::path(workspace_root) / path and assign back to the same variable). Update the loop over p.compile_commands_paths and the three directory assignments to normalize/absolute them relative to workspace_root when necessary so config values like "build" or ".clice" resolve under the workspace rather than the server CWD.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/server/master_server.cpp`:
- Around line 185-190: The current logic treats any present
initialization_options (even empty "{}") as a valid override because
kota::codec::json::to_json on init.initialization_options yields a non-empty
string and is stored in init_options_json, causing load_from_json /
from_json<CliceConfig> / apply_defaults to shadow file config; change the
handling so that when init.initialization_options exists but serializes to an
empty object (e.g., "{}") or contains no relevant CliceConfig fields you treat
it as absent: after calling
kota::codec::json::to_json(*init.initialization_options) check the parsed JSON
object for emptiness or absence of expected keys before assigning to
init_options_json (or instead perform a merge where explicit fields from the
parsed JSON overlay the result of load_from_workspace rather than replacing it);
update the flow around init.initialization_options, init_options_json,
load_from_json, load_from_workspace, and apply_defaults accordingly and add a
regression test where workspace has clice.toml and client sends {} to ensure
file config wins.
---
Duplicate comments:
In `@src/server/config.cpp`:
- Around line 80-85: The substituted paths (p.cache_dir, p.index_dir,
p.logging_dir and each entry in p.compile_commands_paths) must be anchored to
workspace_root when they are relative; after calling substitute_workspace(...)
detect if the resulting string is a relative path and, if so, prepend/resolve it
against workspace_root (e.g. using std::filesystem::path(workspace_root) / path
and assign back to the same variable). Update the loop over
p.compile_commands_paths and the three directory assignments to
normalize/absolute them relative to workspace_root when necessary so config
values like "build" or ".clice" resolve under the workspace rather than the
server CWD.
In `@src/server/config.h`:
- Around line 52-59: CliceConfig's deserializer currently only exposes the
nested fields project and rules which will drop legacy top-level keys (e.g.,
cache_dir, enable_indexing, worker counts, compile_commands_paths) parsed in
src/server/config.cpp; update the parsing/deserialization so it accepts legacy
flat keys and maps them into the new nested structure (or perform an explicit
migration/aliasing pass) before populating CliceConfig.project and
CliceConfig.rules, leaving annotation<std::vector<CompiledRule>,
serde_schema::skip> compiled_rules behavior unchanged; specifically modify the
code that constructs CliceConfig in src/server/config.cpp to detect legacy keys
and copy them into the corresponding ProjectConfig fields (and translate any
worker/compile_commands settings to their new locations) so existing configs are
preserved.
In `@src/server/master_server.cpp`:
- Around line 103-123: The current auto-scan uses directory_iterator order to
set cdb_path via the try_candidate lambda, which is non-deterministic; change
the logic to collect all matching compile_commands.json paths (use try_candidate
or inline check) into a vector<string> (e.g., candidates), sort the vector
lexicographically, then if candidates is non-empty set cdb_path =
candidates.front(); if candidates.size() > 1 emit a warning listing the extra
candidates (so users know which CDB was chosen). Update the block that currently
iterates workspace_root and uses try_candidate to instead populate, sort,
choose, and warn (referencing cdb_path, try_candidate, workspace_root, and the
directory_iterator loop to locate where to change).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1e979306-131c-4655-b22f-c4c6d5897bf9
📒 Files selected for processing (11)
src/server/compiler.cppsrc/server/config.cppsrc/server/config.hsrc/server/indexer.cppsrc/server/master_server.cppsrc/server/master_server.hsrc/server/workspace.cppsrc/support/glob_pattern.cppsrc/support/glob_pattern.htests/integration/compilation/test_persistent_cache.pytests/unit/server/config_tests.cpp
✅ Files skipped from review due to trivial changes (4)
- src/server/workspace.cpp
- src/support/glob_pattern.h
- src/server/indexer.cpp
- src/server/compiler.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
- src/server/master_server.h
- tests/integration/compilation/test_persistent_cache.py
- src/support/glob_pattern.cpp
Restructure CliceConfig into nested ProjectConfig with per-file compilation rules, XDG cache paths, initializationOptions support, and auto-scanning CDB discovery. - Add [[rules]] support with pre-compiled glob pattern matching - Default cache/index/log paths to $XDG_CACHE_HOME/clice/<hash>/ - Accept config via LSP initializationOptions JSON - Auto-scan workspace root + immediate subdirs for compile_commands.json - Use eventide defaulted<T> for optional TOML/JSON fields - Make GlobPattern::match() const Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The PR's check-format CI job was failing because of include ordering and line-wrapping. Rerun the project formatter and commit the result. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5891b35 to
0b1dec1
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (2)
src/server/master_server.cpp (2)
103-123:⚠️ Potential issue | 🟠 MajorMake CDB auto-scan deterministic before choosing a match.
directory_iteratororder is filesystem-dependent, so workspaces with multiple immediate build dirs can load differentcompile_commands.jsonfiles across machines/runs. Collect matching candidates, sort them, then pick a stable winner and optionally warn on ambiguity.Suggested direction
- if(!try_candidate(workspace_root)) { + if(!try_candidate(workspace_root)) { + std::vector<std::string> candidates; std::error_code ec; for(llvm::sys::fs::directory_iterator it(workspace_root, ec), end; it != end && !ec; it.increment(ec)) { if(it->type() == llvm::sys::fs::file_type::directory_file) { - if(try_candidate(it->path())) - break; + auto candidate = path::join(it->path(), "compile_commands.json"); + if(llvm::sys::fs::exists(candidate)) + candidates.push_back(std::move(candidate)); } } + std::ranges::sort(candidates); + if(!candidates.empty()) { + if(candidates.size() > 1) + LOG_WARN("Multiple compile_commands.json files found; using {}", candidates.front()); + cdb_path = std::move(candidates.front()); + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/master_server.cpp` around lines 103 - 123, Auto-scan currently picks the first compile_commands.json found via directory_iterator which is non-deterministic; update the logic around try_candidate, cdb_path and the directory iteration over workspace_root to collect all matching candidates into a vector, sort them (e.g., lexicographically by path), then select a single deterministic winner to assign to cdb_path and emit a warning if more than one candidate was found to indicate ambiguity; ensure try_candidate is used only to test paths and that the sorted selection replaces the existing early-exit behavior so results are reproducible across machines/runs.
185-190:⚠️ Potential issue | 🟠 MajorDo not let empty
initializationOptionsshadowclice.toml.An empty JSON object is still a non-empty string, so
{}will take the initializationOptions path, load defaults, and skip file-based config. Treat empty/unrelated initialization options as absent, or merge explicit JSON fields over the workspace config.Suggested direction
if(init.initialization_options.has_value()) { auto json = kota::codec::json::to_json<kota::ipc::lsp_config>(*init.initialization_options); - if(json) + if(json && !is_empty_or_unrelated_config_json(*json)) init_options_json = std::move(*json); }
is_empty_or_unrelated_config_jsonshould reject at least{}/null, and ideally only accept objects containing supported config keys such asprojectorrules.Also applies to: 273-280
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/master_server.cpp` around lines 185 - 190, The current logic accepts any non-null JSON (e.g. `{}`) from init.initialization_options and causes init_options_json to override file-based clice.toml; update the handling so empty or unrelated JSON is treated as absent: add/modify a helper (e.g. is_empty_or_unrelated_config_json) that examines the result of kota::codec::json::to_json<kota::ipc::lsp_config>(*init.initialization_options) and returns false for `{}`, `null`, or objects lacking supported keys (like "project" or "rules"); only move/assign to init_options_json when that helper returns true. Apply the same check in both places where init.initialization_options is parsed (the current block around init_options_json and the similar block at lines 273-280) so file-based config isn't shadowed by empty/unrelated JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/server/master_server.cpp`:
- Around line 103-123: Auto-scan currently picks the first compile_commands.json
found via directory_iterator which is non-deterministic; update the logic around
try_candidate, cdb_path and the directory iteration over workspace_root to
collect all matching candidates into a vector, sort them (e.g.,
lexicographically by path), then select a single deterministic winner to assign
to cdb_path and emit a warning if more than one candidate was found to indicate
ambiguity; ensure try_candidate is used only to test paths and that the sorted
selection replaces the existing early-exit behavior so results are reproducible
across machines/runs.
- Around line 185-190: The current logic accepts any non-null JSON (e.g. `{}`)
from init.initialization_options and causes init_options_json to override
file-based clice.toml; update the handling so empty or unrelated JSON is treated
as absent: add/modify a helper (e.g. is_empty_or_unrelated_config_json) that
examines the result of
kota::codec::json::to_json<kota::ipc::lsp_config>(*init.initialization_options)
and returns false for `{}`, `null`, or objects lacking supported keys (like
"project" or "rules"); only move/assign to init_options_json when that helper
returns true. Apply the same check in both places where
init.initialization_options is parsed (the current block around
init_options_json and the similar block at lines 273-280) so file-based config
isn't shadowed by empty/unrelated JSON.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d76eaa53-adfe-4e38-b103-c6da6c2a22fa
📒 Files selected for processing (11)
src/server/compiler.cppsrc/server/config.cppsrc/server/config.hsrc/server/indexer.cppsrc/server/master_server.cppsrc/server/master_server.hsrc/server/workspace.cppsrc/support/glob_pattern.cppsrc/support/glob_pattern.htests/integration/compilation/test_persistent_cache.pytests/unit/server/config_tests.cpp
✅ Files skipped from review due to trivial changes (2)
- src/support/glob_pattern.h
- src/server/workspace.cpp
🚧 Files skipped from review as they are similar to previous changes (6)
- src/server/master_server.h
- src/server/indexer.cpp
- src/support/glob_pattern.cpp
- src/server/compiler.cpp
- tests/integration/compilation/test_persistent_cache.py
- src/server/config.cpp
- Drop rules whose glob patterns all fail to compile, instead of
silently adding an empty CompiledRule whose append/remove flags
would never be applied to any file.
- Include parser error message in the load_from_json failure log,
matching load()'s behavior.
- Apply match_rules() in init_compile_graph's dependency resolver
for consistency with fill_compile_args().
- Add unit tests covering load_from_json (success + malformed JSON),
malformed/missing TOML, ${workspace} substitution, XDG cache
resolution, invalid glob pattern handling, and defaults-after-JSON
priority.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/server/compiler.cpp (1)
217-218:⚠️ Potential issue | 🟡 Minor
match_rulesnot applied to the host CDB lookup in header-context path.
fill_compile_argsand theinit_compile_graphresolver both feed rule-derivedappend/removeintocdb.lookup, but this branch builds the command for a header's host (whose flags become the actual compile command after substitution at Line 228–237) without them. Files compiled via a header context will therefore miss configured append/remove flags that would apply to the host source.Proposed diff
auto host_path = workspace.path_pool.resolve(ctx_ptr->host_path_id); - auto host_results = workspace.cdb.lookup(host_path, {.query_toolchain = true}); + std::vector<std::string> rule_append, rule_remove; + workspace.config.match_rules(host_path, rule_append, rule_remove); + auto host_results = workspace.cdb.lookup(host_path, + {.query_toolchain = true, + .remove = rule_remove, + .append = rule_append});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/compiler.cpp` around lines 217 - 218, The host CDB lookup for header-context paths (resolving host_path from ctx_ptr->host_path_id then calling workspace.cdb.lookup) is missing rule-derived modifications; change the lookup to include the same match_rules/append/remove options used by fill_compile_args and init_compile_graph so the host lookup receives ctx_ptr->match_rules (or equivalent append/remove fields) in its lookup parameters (e.g., pass {.query_toolchain = true, .match_rules = ctx_ptr->match_rules} or the struct that carries append/remove) so header-context compilations get the rule-derived flags applied.
♻️ Duplicate comments (1)
src/server/config.cpp (1)
84-85:⚠️ Potential issue | 🟠 MajorRelative
compile_commands_pathsstill resolved against CWD, not workspace root.
substitute_workspaceonly expands the${workspace}placeholder; a plain relative entry like"build"is passed through unchanged and later resolved against the server process CWD insrc/server/master_server.cpp. Configured CDB discovery in common setups (where users writecompile_commands_paths = ["build"]) will fail to locate the file.Proposed patch
- for(auto& path: p.compile_commands_paths) - substitute_workspace(path, workspace_root); + for(auto& cdb_path: p.compile_commands_paths) { + substitute_workspace(cdb_path, workspace_root); + if(!workspace_root.empty() && !llvm::sys::path::is_absolute(cdb_path)) + cdb_path = path::join(workspace_root, cdb_path); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/config.cpp` around lines 84 - 85, substitute_workspace only expands the ${workspace} token but leaves plain relative entries like "build" unchanged; update the loop that processes p.compile_commands_paths to, after calling substitute_workspace(path, workspace_root), detect if the resulting path is not absolute and then resolve it by joining it with workspace_root (so relative entries become workspace-root-relative instead of CWD-relative). Refer to p.compile_commands_paths, substitute_workspace(path, workspace_root), and workspace_root when implementing this fix.
🧹 Nitpick comments (1)
src/server/config.cpp (1)
89-109: IncludeGlobPattern::createerror detail in the log.
GlobPattern::createreturns a descriptivestd::stringinstd::unexpected(multiple/, brace expansion failures, sub-pattern errors), but the warning only prints the raw pattern string, leaving users to guess why the pattern was rejected.Proposed diff
- auto pat = GlobPattern::create(pattern_str); - if(!pat) { - LOG_WARN("Invalid glob pattern in rule: {}", pattern_str); - continue; - } + auto pat = GlobPattern::create(pattern_str); + if(!pat) { + LOG_WARN("Invalid glob pattern in rule: '{}' ({})", pattern_str, pat.error()); + continue; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/config.cpp` around lines 89 - 109, The warning for invalid glob patterns should include the error message returned by GlobPattern::create instead of only the raw pattern; update the branch where pat is falsy to extract the descriptive error (from the returned unexpected/error payload of GlobPattern::create) and pass it into LOG_WARN along with pattern_str so users see why compilation failed (keep the existing check of compiled.patterns.empty(), and adjust the invalid-pattern log in the loop that handles pat failures inside the rule-processing loop).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/unit/server/config_tests.cpp`:
- Around line 204-215: The test TEST_CASE(XdgCacheDir) mutates the process
environment with ::setenv/::unsetenv which clobbers any existing XDG_CACHE_HOME
and is non-portable/racy; change it to save the prior value (via getenv), set
the test value, run CliceConfig::apply_defaults, then restore the original value
(reinstating or clearing XDG_CACHE_HOME as it was) or, better, use a small RAII
helper (e.g. ScopedEnv) to set and automatically restore the variable around the
test; update references in the test (TempDir, ::setenv/::unsetenv, CliceConfig
config) to use this restore-safe approach so the environment is not permanently
changed and works on MSVC/parallel runs.
---
Outside diff comments:
In `@src/server/compiler.cpp`:
- Around line 217-218: The host CDB lookup for header-context paths (resolving
host_path from ctx_ptr->host_path_id then calling workspace.cdb.lookup) is
missing rule-derived modifications; change the lookup to include the same
match_rules/append/remove options used by fill_compile_args and
init_compile_graph so the host lookup receives ctx_ptr->match_rules (or
equivalent append/remove fields) in its lookup parameters (e.g., pass
{.query_toolchain = true, .match_rules = ctx_ptr->match_rules} or the struct
that carries append/remove) so header-context compilations get the rule-derived
flags applied.
---
Duplicate comments:
In `@src/server/config.cpp`:
- Around line 84-85: substitute_workspace only expands the ${workspace} token
but leaves plain relative entries like "build" unchanged; update the loop that
processes p.compile_commands_paths to, after calling substitute_workspace(path,
workspace_root), detect if the resulting path is not absolute and then resolve
it by joining it with workspace_root (so relative entries become
workspace-root-relative instead of CWD-relative). Refer to
p.compile_commands_paths, substitute_workspace(path, workspace_root), and
workspace_root when implementing this fix.
---
Nitpick comments:
In `@src/server/config.cpp`:
- Around line 89-109: The warning for invalid glob patterns should include the
error message returned by GlobPattern::create instead of only the raw pattern;
update the branch where pat is falsy to extract the descriptive error (from the
returned unexpected/error payload of GlobPattern::create) and pass it into
LOG_WARN along with pattern_str so users see why compilation failed (keep the
existing check of compiled.patterns.empty(), and adjust the invalid-pattern log
in the loop that handles pat failures inside the rule-processing loop).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8c517bfd-2e6b-43ab-ac7b-5cf76e957849
📒 Files selected for processing (3)
src/server/compiler.cppsrc/server/config.cpptests/unit/server/config_tests.cpp
Correctness:
- Apply match_rules() in fill_header_context_args so that user-defined
[[rules]] apply uniformly to header files served via header-context
fallback, not just via the normal CDB lookup path.
- substitute_workspace() is now a no-op when workspace_root is empty,
avoiding bogus paths like "/cache" from "${workspace}/cache".
- load() now parses TOML with toml++ directly to capture line, column
and description on failure (kotatsu's wrapper discarded this). The
warning is promoted to LOG_ERROR and a follow-up warning is emitted
when load_from_workspace() falls back because the file is malformed.
Style:
- Drop unused <thread> include.
- Migrate const std::string& path/workspace_root parameters to
llvm::StringRef per project convention.
- Remove using-declarations from config.h to avoid leaking kota::meta
names into every TU that includes it.
- Rename shadowing "path" loop variable.
- Replace noisy std::string() comparisons with .empty() in tests.
Tests (+8, total 545):
- XdgHashUnique: different workspaces get different hashed cache dirs.
- HomeFallback: $HOME/.cache/clice/<hash> used when XDG is unset.
- WorkspaceCacheFallback: ${workspace}/.clice used when both unset.
- WorkspaceSubstEmpty: empty workspace_root leaves placeholder intact.
- WorkspaceSubstRepeated: multiple ${workspace} in one string.
- CompilePathsList: substitution applied per compile_commands_paths entry.
- TomlErrorLocated / WorkspaceMalformedFallback: malformed clice.toml
returns nullopt from load() and falls back to defaults from
load_from_workspace().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Revert the toml++ direct-parse workaround; kotatsu's serde_error already carries source_location (populated for schema errors) and formats it via to_string(), so bypassing the wrapper was unnecessary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (4)
tests/unit/server/config_tests.cpp (1)
315-321:WorkspaceSubstRepeatedis pinning a surprising behavior — consider asserting post-normalization instead.Expected value
"/root/a//root/b"contains a//that comes purely from naive string replacement (user-written/${workspace}+workspace_root = "/root"). Any future PR that normalizes output (e.g., collapsing duplicate separators, whichllvm::sys::pathhelpers sometimes do) would break this test even though it would be an improvement. If the point is "every occurrence is substituted" rather than "no normalization happens", consider asserting both halves independently or counting occurrences, so the test doesn't pin a debatable artifact.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/server/config_tests.cpp` around lines 315 - 321, The test WorkspaceSubstRepeated pins an implementation artifact by expecting "/root/a//root/b" after CliceConfig::apply_defaults substitutes project.cache_dir; instead update the test to assert the semantic intent (that every "${workspace}" occurrence is substituted) rather than exact raw string with duplicate separators—for example, verify that project.cache_dir contains two occurrences of "/root" in the expected positions or assert the two path segments independently after normalization; locate the TEST_CASE WorkspaceSubstRepeated and CliceConfig.project.cache_dir and change the EXPECT_EQ to an assertion that checks occurrence count or compares normalized segments so future path-normalization changes won't break the test.src/server/config.cpp (2)
46-55: XDG cache key isworkspace_rootas-received — not normalized.
xxh3_64bits(workspace_root)is computed on the raw string passed by the caller, so logically-equivalent roots (/home/user/proj,/home/user/proj/,/home/user/./proj, a path with a different case on case-insensitive FS, a symlinked path) map to distinct cache dirs. That means the same project opened two slightly different ways on the same machine will cold-start the index twice and consume extra disk. Consider canonicalizing viallvm::sys::fs::real_path/remove_dots(with a safe fallback when the path doesn't yet exist) before hashing. Low-severity but worth doing while the layout is fresh.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/config.cpp` around lines 46 - 55, The cache key is computed from the raw workspace_root string (xxh3_64bits(workspace_root)), causing equivalent paths to map to different directories; normalize the path before hashing by resolving dots/symlinks and canonicalizing where possible (use llvm::sys::fs::real_path or remove_dots) and fall back to a safe normalization when the path does not exist (e.g., remove_dots or weakly canonicalize and normalize case on case-insensitive platforms) so that workspace_root is normalized prior to calling xxh3_64bits; update the code around workspace_root, xxh3_64bits, and the path::join creation so the hash is computed from the normalized_path and keep existing create_directories logic unchanged.
188-195: Log string split is a formatter trap.Adjacent string-literal concatenation between a trailing-space literal and the format string works, but it means the format string is assembled at compile time across two literals — easy to silently lose a
{}-count when someone edits only one half. Since this already logsworker_memory_limitthat isn't part of what ends up happening (e.g., a user-overriddenmemory_limit), consider just writing a single contiguous format string.Nit
- LOG_INFO( - "No clice.toml found, using default configuration " "(stateful={}, stateless={}, memory_limit={}MB)", - config.project.stateful_worker_count.value, - config.project.stateless_worker_count.value, - config.project.worker_memory_limit.value / (1024 * 1024)); + LOG_INFO("No clice.toml found, using default configuration " + "(stateful={}, stateless={}, memory_limit={}MB)", + config.project.stateful_worker_count.value, + config.project.stateless_worker_count.value, + config.project.worker_memory_limit.value / (1024 * 1024));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/config.cpp` around lines 188 - 195, The log message in the fallback branch uses adjacent string-literal concatenation which risks mismatched format placeholders; update the LOG_INFO call in this block (the call that follows CliceConfig config; config.apply_defaults(workspace_root);) to use a single contiguous format string that includes all three placeholders in the correct order for config.project.stateful_worker_count.value, config.project.stateless_worker_count.value, and config.project.worker_memory_limit.value / (1024 * 1024), ensuring spacing and braces are correct so the formatter and arguments cannot get out of sync.src/server/config.h (1)
33-39: Intentional split betweendefaulted<>andstd::optional<>is fine, just worth calling out.
enable_indexing/idle_timeout_msusestd::optionalspecifically soapply_defaultscan distinguish "unset" from an explicitfalse/0(seeApplyDefaultsPreserveSet). The rest usedefaulted<>because their zero/empty sentinel is also a valid "unset" signal. Consider a brief comment on these two lines so future contributors don't "normalize" them todefaulted<>and silently break user-overrides offalse.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/server/config.h` around lines 33 - 39, Add a brief clarifying comment above the two members so future maintainers know why enable_indexing and idle_timeout_ms use std::optional instead of kota::meta::defaulted: explain that ApplyDefaultsPreserveSet needs to distinguish "unset" from explicit values (false/0) so enable_indexing and idle_timeout_ms must be optional, whereas stateful_worker_count/stateless_worker_count/worker_memory_limit use defaulted<> because their zero/empty sentinel can represent "unset". Reference the member names enable_indexing, idle_timeout_ms and the function/behavior ApplyDefaultsPreserveSet in the comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/server/config.cpp`:
- Around line 46-55: The cache key is computed from the raw workspace_root
string (xxh3_64bits(workspace_root)), causing equivalent paths to map to
different directories; normalize the path before hashing by resolving
dots/symlinks and canonicalizing where possible (use llvm::sys::fs::real_path or
remove_dots) and fall back to a safe normalization when the path does not exist
(e.g., remove_dots or weakly canonicalize and normalize case on case-insensitive
platforms) so that workspace_root is normalized prior to calling xxh3_64bits;
update the code around workspace_root, xxh3_64bits, and the path::join creation
so the hash is computed from the normalized_path and keep existing
create_directories logic unchanged.
- Around line 188-195: The log message in the fallback branch uses adjacent
string-literal concatenation which risks mismatched format placeholders; update
the LOG_INFO call in this block (the call that follows CliceConfig config;
config.apply_defaults(workspace_root);) to use a single contiguous format string
that includes all three placeholders in the correct order for
config.project.stateful_worker_count.value,
config.project.stateless_worker_count.value, and
config.project.worker_memory_limit.value / (1024 * 1024), ensuring spacing and
braces are correct so the formatter and arguments cannot get out of sync.
In `@src/server/config.h`:
- Around line 33-39: Add a brief clarifying comment above the two members so
future maintainers know why enable_indexing and idle_timeout_ms use
std::optional instead of kota::meta::defaulted: explain that
ApplyDefaultsPreserveSet needs to distinguish "unset" from explicit values
(false/0) so enable_indexing and idle_timeout_ms must be optional, whereas
stateful_worker_count/stateless_worker_count/worker_memory_limit use defaulted<>
because their zero/empty sentinel can represent "unset". Reference the member
names enable_indexing, idle_timeout_ms and the function/behavior
ApplyDefaultsPreserveSet in the comment.
In `@tests/unit/server/config_tests.cpp`:
- Around line 315-321: The test WorkspaceSubstRepeated pins an implementation
artifact by expecting "/root/a//root/b" after CliceConfig::apply_defaults
substitutes project.cache_dir; instead update the test to assert the semantic
intent (that every "${workspace}" occurrence is substituted) rather than exact
raw string with duplicate separators—for example, verify that project.cache_dir
contains two occurrences of "/root" in the expected positions or assert the two
path segments independently after normalization; locate the TEST_CASE
WorkspaceSubstRepeated and CliceConfig.project.cache_dir and change the
EXPECT_EQ to an assertion that checks occurrence count or compares normalized
segments so future path-normalization changes won't break the test.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 62255d15-b86a-4f56-8133-0f089ed975c6
📒 Files selected for processing (4)
src/server/compiler.cppsrc/server/config.cppsrc/server/config.htests/unit/server/config_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
- src/server/compiler.cpp
- scan_dependency_graph now applies [[rules]] via an optional RuleMatcher callback so -I/-isystem/-std modifications are visible to include resolution — previously only fill_compile_args honored rules, leaving rule-affected files with unresolved includes in the dependency graph. - initializationOptions is now layered on top of the workspace config instead of replacing it: load clice.toml first, overlay JSON fields that are present, then re-apply defaults. Partial overrides no longer silently drop unrelated toml settings like rules and cache paths. - match_rules processes rules in declaration order and cancels earlier appends when a later rule removes the same flag. Lookup applies removals only against base CDB flags, so without this fold a later rule could not override what an earlier matching rule appended. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
POSIX setenv/unsetenv aren't part of the Windows CRT, so the XDG fallback tests failed to compile with MSVC. Wrap the calls in small helpers that dispatch to _putenv_s on Windows (passing an empty value removes the variable). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
path::join produces native separators on Windows, so cache_dir assertions comparing against literal forward-slash paths failed under Windows CI. Normalize with path::convert_to_slash before comparing, matching the pattern already used in command_tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Options rules Covers five scenarios: baseline (no rules), TOML-declared rules, rules sent via LSP initializationOptions, initializationOptions replacing TOML rules, and patterns that don't match. Each test's main.cpp references a macro that is only defined when the rule is applied, so rule handling is observable through compiler diagnostics. Adds an @pytest.mark.init_options marker so tests can pass initialization options through the client fixture.
Summary
[[rules]]: TOML array-of-tables config for per-file compilation flag rules with glob pattern matching (append/remove). Patterns are pre-compiled at config load time. Rules whose patterns all fail to compile are dropped entirely (no silent no-op entries), and rules now apply uniformly to every compilation — including the header-context fallback path used when editing a header without its own CDB entry.compile_commands.json, replacing the hardcoded directory list.initializationOptions: Clients can pass config as JSON via the LSP initialize request; priority isinitializationOptions > clice.toml > defaults.$XDG_CACHE_HOME/clice/<workspace-hash>/; fall back to$HOME/.cache/clice/<hash>/, then<workspace>/.clice/.${workspace}substitution: supported incache_dir,index_dir,logging_dir, and everycompile_commands_pathsentry. No-op whenworkspace_rootis empty.kota::meta::defaulted<T>, so minimal config files work correctly.clice.tomlnow logs line, column and parser description (via toml++ direct parse); a malformed workspace config surfaces a clear fallback warning instead of silently reverting to defaults.Test plan
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Refactor
Tests